package net.sourceforge.fidocadj.dialogs;

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.border.*;
import javax.swing.plaf.metal.*;

import net.sourceforge.fidocadj.globals.*;

import java.awt.*;
import java.io.*;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;
import java.util.*;

/**
* JFileChooser accessory panel for listing libraries description.
*
* @author Kohta Ozaki
*/
public class LibraryPanel extends JPanel implements PropertyChangeListener
{
    final private static int PREFERRED_PANEL_WIDTH = 250;

    final private JFileChooser fc;
    final private LibraryListModel listModel;

    /**
    * Creates UI.
    * This LibraryPanel register as accessory panel to JFileChooser.
    * And adds as listener for receiving selection change event.
    * @param fc JFileChooser instance.
    */
    public LibraryPanel(JFileChooser fc)
    {
        this.fc = fc;
        fc.addPropertyChangeListener(this);
        fc.setAccessory(this);
        listModel = new LibraryListModel();

        SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run()
                {
                    initGUI();
                }
        } );

        listModel.setDirectory(fc.getCurrentDirectory());
    }

    /**
    * Creates UI.
    */
    private void initGUI()
    {
        JList<LibraryDesc> fileList;
        JScrollPane sp;

        setLayout(new BorderLayout());
        setPreferredSize(new Dimension(PREFERRED_PANEL_WIDTH,1));

        // If this class is run as a standalone program, the Globals.messages
        // resource handler might not be initizalized. In this case,
        // an english tag will do the job.
        if(Globals.messages==null)
            add(BorderLayout.NORTH, new JLabel("Libraries in directory:"));
        else
            add(BorderLayout.NORTH,new JLabel(
                Globals.messages.getString("lib_in_dir")));


        fileList = new JList<LibraryDesc>(listModel);
        fileList.setCellRenderer(new ListCellRenderer<LibraryDesc>() {
            @Override
            public Component getListCellRendererComponent(JList<?
                extends LibraryPanel.LibraryDesc> list,
                LibraryPanel.LibraryDesc value, int index,
                boolean isSelected, boolean cellHasFocus)
            {
                LibraryDesc desc = (LibraryDesc) value;
                String libraryName;
                JPanel p;
                Icon icon = MetalIconFactory.getTreeFloppyDriveIcon();
                Icon spaceIcon = new SpaceIcon(icon.getIconWidth(),
                    icon.getIconHeight());

                if (desc.libraryName == null) {
                    libraryName = "---";
                } else {
                    libraryName = "(" + desc.libraryName + ")";
                }

                p = new JPanel();
                p.setBorder(new EmptyBorder(2,0,3,0));
                p.setOpaque(false);
                p.setLayout(new BorderLayout());
                p.add(BorderLayout.NORTH, new JLabel(desc.filename, icon,
                    SwingConstants.LEFT));
                p.add(BorderLayout.SOUTH, new JLabel(libraryName, spaceIcon,
                    SwingConstants.LEFT));
                return p;
            }
        });

        sp = new JScrollPane(fileList);
        sp.setVerticalScrollBarPolicy(
            ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
        add(BorderLayout.CENTER, sp);

        // Disable focus.
        // The list is never selected.
        fileList.setFocusable(false);
    }

    @Override
    public void propertyChange(PropertyChangeEvent evt)
    {

        if (evt.getPropertyName().equals(
            JFileChooser.SELECTED_FILE_CHANGED_PROPERTY))
        {
            listModel.setDirectory(fc.getSelectedFile());
        }
        if (evt.getPropertyName().equals(
            JFileChooser.DIRECTORY_CHANGED_PROPERTY))
        {
            listModel.setDirectory(fc.getCurrentDirectory());
        }
    }

    /** For test method.
        Shows only JFileChooser with this LibraryPanel.
        @param args the input parameters on the command line.
    */
    public static void main(String... args)
    {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run()
            {
                JFileChooser fc;
                fc = new JFileChooser();
                fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
                fc.setPreferredSize(new Dimension(800,400));
                new LibraryPanel(fc);

                fc.showOpenDialog(null);
            }
        });
    }

    /**
    * ListModel to provide libraries list.
    * This model searches libraries in selected directory.
    * And provide library name and filename to JList component.
    */
    public class LibraryListModel implements ListModel<LibraryDesc>
    {
        final private ArrayList<ListDataListener> listeners;
        final private ArrayList<LibraryDesc> libraryList;
        private File currentDir;

    /** Constructs model.
    */
        LibraryListModel()
        {
            listeners = new ArrayList<ListDataListener>();
            libraryList = new ArrayList<LibraryDesc>();
        }

        /**
        * Sets directory.
        * And updates libraries list in directory.
        * @param dir selected directory as File
        */
        public void setDirectory(File dir)
        {
            currentDir = dir;

            clearList();
            if (currentDir != null && currentDir.canRead() &&
                 currentDir.isDirectory())
            {
                // Permission check
                refreshList();
            }
            // DB -> KO check if it is correct. I removed a "}"
            fireChanged();
        }

        private void refreshList()
        {
            File[] files;
            LibraryDesc desc;

            files = currentDir.listFiles(new FileFilter() {
                @Override
                public boolean accept(File f)
                {
                    if (f.isFile() &&
                        f.getName().toLowerCase().matches("^.*\\.fcl$"))
                    {
                        return true;
                    }
                    // DB -> KO check if it is correct. I removed a "}"
                    return false;
                }
            });

            if(files==null)
                return;
            for (File f : files) {
                desc = new LibraryDesc();
                desc.filename = f.getName();
                desc.libraryName = getLibraryName(f);
                libraryList.add(desc);
            }

            // Sort list by filename.
            Collections.sort(libraryList, new Comparator<LibraryDesc>() {
                @Override
                public int compare(LibraryDesc ld1, LibraryDesc ld2)
                {
                    // Sort with case sensitive.
                    // This is usually made in UNIX file systems.
                    // If case sensitive is not needed, use
                    // String.compareToIgnoreCase
                    return ld1.filename.compareTo(ld2.filename);
                }
                /*@Override
                public boolean equals(Object obj)
                {   // DB. FindBugs complains that this methods always
                    // returns "false". It considers it a quite high priority
                    // issue to be solved. Is there any particular reason why
                    // this must return false?
                    // return false;
                    return this == obj;
                }*/
            });
        }

        private void clearList()
        {
            libraryList.clear();
        }

        private void fireChanged()
        {
            for (ListDataListener l : listeners) {
                l.contentsChanged(new ListDataEvent(this,
                    ListDataEvent.CONTENTS_CHANGED, 0, 0));
            }
        }

        private String getLibraryName(File f)
        {
            // if there is a public api for reading library name direct,
            // rewrite this section.

            // maxReadLine = -1  : reads to EOF.
            //             = num : reads to num line.
            int maxReadline = -1;
            int pt = 0;

            FileReader fr = null;
            BufferedReader br = null;

            String buf;
            String libraryName = null;

            try {
                fr = new FileReader(f);
                br = new BufferedReader(fr);

                while (true) {
                    buf = br.readLine();

                    // check EOF
                    if (buf == null) {
                        break;
                    }

                    if (buf.matches("^\\[FIDOLIB .*\\]\\s*")) {
                        buf = buf.trim();
                        libraryName = buf.substring(9, buf.length() - 1);
                        break;
                    }

                    if (maxReadline != -1 && maxReadline <= pt) {
                        break;
                    }
                    pt++;
                }

                // DB: it seems to me that catching an Exception is a
                // little bit too general. Which kind of reasonable
                // problems have we got to handle here?
            } catch (Exception e) {
                // return null for libraryName
                libraryName=null;
            } finally {
                try {
                    if (br != null) {
                        br.close();
                    }
                    if (fr != null) {
                        fr.close();
                    }
                    // DB: it seems to me that catching an Exception is a
                    // little bit too general. Which kind of reasonable
                    // problems have we got to handle here?
                } catch (Exception e) {
                    System.out.println("Problems while closing streams.");
                }
            }

            return libraryName;
        }

        @Override
        public void addListDataListener(ListDataListener l)
        {
            listeners.add(l);
        }

        @Override
        public void removeListDataListener(ListDataListener l)
        {
            listeners.remove(l);
        }

        @Override
        public int getSize()
        {
            return libraryList.size();
        }

        @Override
        public LibraryDesc getElementAt(int index)
        {
            return libraryList.get(index);
        }
    }

    /**
    * Library description class.
    */
    private class LibraryDesc
    {

        public String filename;
        public String libraryName;

        @Override
        public String toString()
        {
            return String.format("%s (%s)", filename, libraryName);
        }
    }

    /**
    * Dummy icon class for spacing.
    */
    private class SpaceIcon implements Icon
    {

        final private int width;
        final private int height;

        /** Constructor. Creates a dummy icon with the given size.
        */
        SpaceIcon(int width, int height)
        {
            this.width = width;
            this.height = height;
        }

        @Override
        public int getIconHeight()
        {
            return height;
        }

        @Override
        public int getIconWidth()
        {
            return width;
        }

        @Override
        public void paintIcon(Component c, Graphics g, int x, int y)
        {
            // NOP
        }
    }
}

